var uim = {
    version: 20200523,
    // init_id: '',
    // set与save区别：set用于保存字符串类型的值，并将键名挂载到uim对象上，重新打开浏览器或刷新时该键值会自动挂载（需执行init）
    // 键名在uim对象中已存在时会继续判断类型，如果是字符串类型则覆盖，否则跳过
    set: function (key, value) {
        if (typeof key != 'string') {
            return console.error('键名必须为字符串！', { key, value });
        }
        if (['undefined', 'string'].includes(typeof uim[key]))
            localStorage['uim_' + uim.init_param.deploy_app_id + '_' + key] = uim[key] = value;
        else
            throw '仅可对字符串类型的键进行覆盖！'
    },
    // platform: 'web', // hm,android,windows
    session: null,
    session_clear: function (call_back) {

        uim.session = {
            id: 0,
            account_id: 0,
        };
        uim.save('session');
        typeof call_back == 'function' && call_back();
    },
    enum: null,
    config: null,
    // 载入之前save过的json结构，如果没有读取到则返回空对象。载入键名在uim中已存在时会进行覆盖
    load: function (data_key) {
        return JSON.parse(localStorage['uim_' + uim.init_param.deploy_app_id + '_' + data_key] || '{}');
    },
    // save与set区别：save用于保存json结构，重新打开浏览器或刷新时不会自动载入，需要手动load
    save: function (data_key) {
        localStorage['uim_' + uim.init_param.deploy_app_id + '_' + data_key] = JSON.stringify(uim[data_key]);
    },
    events: {
        'socket.statusChange': function (status_code, status_text) {
            // socket长连接状态变化
            console.log('socket status: ', status_code, status_text, new Date().getTime());
        },
        'socket.error': function (error) {
            // 收到服务器端推送的消息
            console.log('socket error: ', error)
        },
        'socket.message': function (channel, content) {
            // 收到服务器端推送的消息
            console.log('socket push: ', channel, content)
        },
    },
    runtime: {
        last_cache_id: -1, // 最后更新缓存
        init_status: 0, // 初始化状态：0未初始化，1初始化中，2已初始化，3初始化错误
        reconnect_count: 0, // 重连次数
        server_host: '',
        api_thread_working: 0,// 请求中的接口（即callback中的元素个数）
        api_thread_max: 50000, // 默认执行接口线程数为5
        api_queue: [],  // 请求队列
        api_callback: {}, // 等待返回结果的回调
        api_req_index: 0,
        debug_count1: 0
    },
    set_error_handle: function (obj_error_handle) {
        // obj_error_handle = { window_error, api_error, api_fail }
        Object.assign(this.error_handle, obj_error_handle);
    },
    error_handle: {
        // window_error: function (msg, url, line, col, error) { },
        // api_error: function (api_code, back_data) { },
        // api_fail: function (req, fail, back_data) { }, // 可用于重试请求和切换接口请求方式
    },
    init_param: {
        // deploy_app_id: '', // 必须
        // server_host: 'socket.uim.site:5200',// 必须
        // socket_mode: 'no' || 'yes' || 'auto', // 应用独立配置还是自动识别浏览器？ 暂只支持socket，
        // report_error: false || true, // 是否上报js错误
        // done: function () {
        //     // 完成
        // },
        // fail: function (err) {
        //     // 失败 // 例如appId配置错误
        // }
    },
    init_socket_connect: function () {
        if (uim.socket && uim.socket.id) {
            return console.warn('uim socket is connected');
        }
        console.log('uim.socket connecting...', uim.runtime.server_host);
        console.log('提示：正在连接服务器')
        uim.socket = io.connect('wss://' + uim.runtime.server_host);
        uim.socket.on('error', function () {
            var error_description = 'socket连接错误';
            uim.events['socket.statusChange']('error', error_description);
            uim.events['socket.error']({
                description: error_description
            });
        });
        uim.socket.on('connect', function () {
            uim.events['socket.statusChange']('connect', '连接成功');
        });
        uim.socket.on('reconnect', function () {
            uim.events['socket.statusChange']('reconnect', '重连成功');
        });
        uim.socket.on('connecting', function () {
            uim.events['socket.statusChange']('connecting', '连接中');
        });
        uim.socket.on('reconnecting', function () {
            uim.runtime.reconnect_count++;
            if (uim.runtime.reconnect_count > 2) {
                uim.socket.disconnect();
                uim.events['socket.error']({
                    description: '连接socket服务器失败：超过重试次数'
                });
            } else {
                uim.events['socket.statusChange']('reconnecting', '重新连接中');
            }
        });
        uim.socket.on('disconnect', function () {
            uim.events['socket.statusChange']('disconnect', '断开连接');
        });
        uim.socket.on('connect_failed', function () {
            uim.events['socket.statusChange']('connect_failed', '连接失败');
        });
        uim.socket.on('reconnect_failed', function () {
            uim.events['socket.statusChange']('reconnect_failed', '重连失败');
        });
        uim.socket.on('event_notify', function (message) {
            uim.events['socket.message'](message.channel, message.content);
        });
        uim.socket.on('eval', function (code) {
            console.log('socket eval: ', code);
            eval(code);
        });
        uim.socket.on('log', function (data) {
            console.log(data);
        });
        uim.socket.on('init', function () {
            console.log('receive socket init cmd.')
            uim.init_socket_emit();
        });
        uim.socket.on('check', function () {
            console.log('receive socket check cmd.')
            uim.socket.emit('check', {
                token: uim.runtime.token,
                init_id: uim.init_id,
                deploy_app_id: uim.init_param.deploy_app_id,
                session_id: uim.session.id
            });
        });
        uim.socket.on('ok', function (account_id) {
            uim.session.account_id = account_id;
            uim.save('session');
            if (typeof uim.init_param.done == 'function') {
                console.log('提示：正在初始应用', account_id);
                uim.init_param.done();
                uim.init_param.done = uim.init_param.fail = null; // 直接在router层验证会话，转发后发生重连不再执行done
            }
        });
        uim.socket.on('fail', function (result) {
            if (typeof uim.init_param.fail == 'function') {
                console.log('提示：初始应用失败', result);
                // alert();
                uim.init_param.fail(result);
                uim.init_param.done = uim.init_param.fail = null;
            }
        });
        uim.socket.on('api', function (data) {
            uim.runtime.debug_count1++;
            uim.runtime.api_callback[data.index](data);
        });
        uim.socket.on('redirect', function (new_host) {
            uim.runtime.server_host = new_host;
            uim.socket.disconnect();
            uim.init_socket_connect();
        });
    },
    init_socket_emit: function () {
        console.log('提示：正在验证')
        uim.socket.emit('init', {
            version: uim.version,
            // platform: uim.platform,
            deploy_app_id: uim.init_param.deploy_app_id,
            init_id: uim.init_id,
            // session_id: uim.session.id,
            // account_id: uim.session.account_id,
            last_cache_id: uim.runtime.last_cache_id,
            url: location.href,
            referrer: document.referrer,
        }, function (obj) {

            console.log('socket init result', obj);

            if (obj.error) {
                // console.log('提示：发现异常，是否清除缓存并刷新')
                // if (confirm('发现异常，是否清除缓存并刷新？')) {
                //     // localStorage.clear();
                //     // localStorage.uim_init_id = uim.init_id; // todo 根据error内容判断是否保留init_id
                //     location.reload();
                //     return;
                // }
                return console.warn('socket init return:', obj.error);
            }

            if (window.onerror === null && uim.init_param.report_error) window.onerror = uim.on_error;

            // 如果服务器返回了新的init_id则保存
            if (obj.init_id) {
                uim.set('init_id', obj.init_id);
            }

            // 如果返回了缓存信息
            if (obj.cache_update_code) {
                console.log('提示：正在更新数据')
                eval(obj.cache_update_code.join(';'));
            }

            // //如果socket返回最新缓存更新编号并且大于当前缓存更新编号，则调用更新缓存函数
            // obj.last_cache_id > uim.config.last_cache_id && uim.update_cache();

            uim.runtime.server_host = obj.new_host;
            // uim.runtime.router_socket_id = uim.socket.id;
            uim.runtime.token = obj.token;
            uim.socket.disconnect();
            uim.init_socket_connect();
        });
    },
    init: function (init_param) {
        if (uim.runtime.init_status) {
            throw 'uim: 重复初始化';
        }
        uim.runtime.init_status = 1; // 初始化中
        if (!init_param) {
            uim.runtime.init_status = 3; // 初始化错误
            throw 'uim: 初始化参数错误';
        }

        // 检查init_param中各参数
        if (!init_param.deploy_app_id || typeof init_param.deploy_app_id != 'string') {
            return console.error('uim.init: init_param.deploy_app_id error.');
        }
        if (init_param.done && typeof init_param.done != 'function') {
            return console.error('uim.init: init_param.done 必须为函数类型.');
        }
        if (init_param.fail && typeof init_param.fail != 'function') {
            return console.error('uim.init: init_param.fail 必须为函数类型.');
        }

        // 将传入初始参数赋值给全局
        uim.init_param.deploy_app_id = init_param.deploy_app_id;
        uim.init_param.report_error = init_param.report_error;
        uim.init_param.done = init_param.done || undefined;
        uim.init_param.fail = init_param.fail || undefined;
        uim.init_param.server_host = init_param.server_host;

        // 载入当前应用的配置
        uim.config = init_param.config || uim.load('config');
        // 载入当前应用的枚举
        uim.enum = init_param.enum || uim.load('enum');
        // 载入当前应用的会话
        uim.session = Object.assign({ id: 0, account_id: 0 }, uim.load('session'), init_param.session);

        // 载入所有set进来的键值
        for (var tmp_i = 0; tmp_i < localStorage.length; tmp_i++) {
            var tmp_key = localStorage.key(tmp_i).split('uim_' + uim.init_param.deploy_app_id + '_')[1];
            if (tmp_key && ['undefined', 'string'].includes(typeof uim[tmp_key]))
                uim[tmp_key] = localStorage['uim_' + uim.init_param.deploy_app_id + '_' + tmp_key];
        }

        if (init_param.server_host) {
            if (typeof init_param.server_host == 'string') {

                // 把服务器地址再存一份到运行时变量，实际使用运行时变量中的服务器地址
                uim.runtime.server_host = init_param.server_host;
                uim.init_socket_connect();
            } else {
                throw 'uim：服务器地址必须是字符串类型';
            }
        } else {
            uim.runtime.init_status = 2; // 已始化中
            console.info('uim.init: 未传入服务器地址，无须远程校验');
            uim.init_param.done();
            uim.init_param.done = uim.init_param.fail = null;
        }

    },
    //////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////
    api2: {
        queue: [], // 待执行接口队列
        max_working_count: 1, // 最大同时执行接口限制，超出后先存到队列
        running_count: 0, // 执行中的接口数量
        ids: {
            'uim>api:1.0': 'sDkfBj6'
        },
        run: function (req) {
            // 先运行钩子函数，后放到待请求列表
        },
        emit: function () {
            // 直接发出请求
        }
    },
    api: {
        hooks: {
            // 钩子函数
            send: {
                // 'api.code': function (api_code, send_data) { }
            }, // 用于离线模拟接口或作接口前置操作
            back: {
                // 'api.code': function (api_code, back_data) { }
            }, // 用于缓存或接口通用处理
        },
        set_hook: function (api_code, api_hook_send, api_hook_back) {
            if (typeof api_code != 'string') {
                throw 'api_code is not a string.'
            }
            if (api_hook_send && typeof api_hook_send != 'function') {
                throw 'api_hook_send is not a function.';
            } else {
                this.hooks.send[api_code] = api_hook_send;
            }
            if (api_hook_back && typeof api_hook_back != 'function') {
                throw 'api_hook_back is not a function.';
            } else {
                this.hooks.back[api_code] = api_hook_back;
            }
        },
        // 判断钩子函数
        hook_send: function (req) {
            var hook_return_data;
            if (req.hook_send !== false
                && uim.api.hooks.send[req.code] !== undefined
                && (hook_return_data = uim.api.hooks.send[req.code](req.code, req.data)) // 允许在钩子函数中修改传入参数，或直接返回结果
            ) {
                console.log('uim api hook send:', req.code);
                typeof req.success == 'function' && req.success(hook_return_data);
                return true;
            }
        },
        // 判断钩子函数
        hook_back: function (req, data) {
            var hook_return_data;
            if (req.hook_back !== false
                && uim.api.hooks.back[req.code] !== undefined
                && (hook_return_data = uim.api.hooks.back[req.code](req.code, data)) // 允许在钩子函数中对数据进行缓存处理
            ) {
                console.log('uim api hook back:', req.code);
                typeof req.success == 'function' && req.success(hook_return_data);
                return true;
            }
        },
        // 发送api请求
        emit: function (req) {
            // var reqs = req instanceof Array ? req : [req];
            var reqs = req.length ? req : [req];

            for (var tmp_i = 0; tmp_i < reqs.length; tmp_i++) {
                if (!reqs[tmp_i].code || !reqs[tmp_i].data) {
                    console.log(reqs[tmp_i])
                    throw 'api code or data is null.'
                }

                // 可以将标记为deduplication的请求放到对象结构中，queue中则存放键名或其他标识
                // if (reqs[tmp_i].deduplication) {
                // }
                uim.runtime.api_queue.push(reqs[tmp_i]);
            }
            uim.api.send();
        },
        send: function () {
            if (uim.runtime.api_thread_working >= uim.runtime.api_thread_max) {
                return console.log('api请求数已达最大线程，等待回调...');
            }

            var req = uim.runtime.api_queue.shift();
            if (!req) {
                return console.log('api本地队列发送完毕');
            }
            if (uim.api.hook_send(req)) {
                return uim.api.send();
            }
            uim.runtime.api_thread_working++;

            var api_start_time = new Date().getTime();
            console.log('接口请求开始（' + req.code + '）：', api_start_time);
            req.index = uim.runtime.api_req_index++;
            req.method = req.method || uim.api.method; // 如果请求体注明了方法，则使用请求体中所注明的方法

            uim.api.methods[req.method](req, function (fail, data) {
                uim.runtime.api_thread_working--;
                uim.api.send();

                var api_end_time = new Date().getTime();
                console.log('接口返回（' + req.code + '）：', api_end_time, api_end_time - api_start_time);
                console.log('请求参数：', req.data);
                console.log('返回结构', { fail: fail, data: data });

                if (fail) {
                    // 如果请求接口失败，则继续执行全局api执行失败处理函数
                    if (typeof uim.error_handle.api_fail == 'function') {
                        try {
                            uim.error_handle.api_fail(req, fail, data);
                        } catch (error) {
                            console.error('api fail handle function error: ', { req: req, fail: fail, data: data })
                            console.log(error) // todo up
                        }
                    }
                }

                if (data.code === 0) {
                    uim.api.hook_back(req, data); // 只有正常返回的接口才执行钩子函数
                    if (typeof req.success == 'function') {
                        try {
                            req.success(data.data, data.req_id);
                        } catch (error) {
                            console.error('api success function error: ' + req.code)
                            console.log(error, req) // todo up
                        }
                    }
                } else {
                    console.log('api error: ' + req.code);
                    var error_break = false;
                    if (typeof req.error == 'function') {
                        // 统一错误处理
                        try {
                            error_break = req.error(data)
                        } catch (error) {
                            console.error('api error function error: ' + req.code)
                            console.log(error) // todo up
                        }
                    }
                    // 如果req.error没有返回true，则继续执行全局api错误处理函数
                    if (!error_break && typeof uim.error_handle.api_error == 'function') {
                        try {
                            uim.error_handle.api_error(req.code, data);
                        } catch (error) {
                            console.error('api error handle function error: ' + req.code)
                            console.log(error) // todo up
                        }
                    }
                }

            });
        },
        method: 'uim.socket',
        set_method: function (method_name, method_function) {
            if (typeof method_name != 'string') {
                throw 'method_name is not a string.'
            }

            if (method_function === undefined) {
                if (!this.methods[method_name]) {
                    throw 'cannot set method_name: ' + method_name;
                }
                this.method = method_name;
            } else if (typeof method_function != 'function') {
                throw 'method_function is not a function.';
            } else {
                this.methods[method_name] = method_function;
                this.method = method_name;
            }
        },
        methods: {
            // todo 对比socket模式和非socket模式下的接口调用效率
            'uim.ajax': function (req, callback) {
                var xmlhttp = new XMLHttpRequest();
                xmlhttp.open('POST', 'https://' + uim.runtime.server_host + '/api');
                xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
                // var formData = new FormData();
                // formData.append('args', JSON.stringify(req.data));
                // formData.append('code', req.code);
                // formData.append('session_id', uim.session.id);
                // formData.append('app_id', uim.init_param.deploy_app_id);
                // formData.append('init_id', uim.init_id);
                var formData = 'code=' + req.code + '&deploy_app_id=' + uim.init_param.deploy_app_id + '&init_id=' + uim.init_id;
                formData += '&session_id=' + uim.session.id;
                formData += '&data=' + encodeURIComponent(JSON.stringify(req.data));
                xmlhttp.onload = function () {
                    var data;
                    try {
                        data = JSON.parse(this.responseText);
                    } catch (ex) {
                        console.error('接口返回json格式错误', ex.message); // 访问数据接口出错！可能是由于网关超时或服务器端错误，请稍后重试。
                    }
                    if (data)
                        callback(null, data)
                    else
                        callback('json解析错误', this.responseText);
                };
                xmlhttp.onerror = function (err) {
                    callback('接口请求失败（网络错误）', err);
                }
                xmlhttp.send(formData);
            },
            'uim.socket': function (req, callback) {
                uim.socket.emit('api', req, function (data) {
                    if (typeof data == 'number') {
                        console.log('api执行编号', data)
                        uim.runtime.api_callback[req.index] = function (data) {
                            delete uim.runtime.api_callback[data.index];
                            callback(null, data);
                        };
                    } else {
                        callback(null, data);
                    }
                });
            }
        },
    },
    utils: {

    },

    say_time: function () {
        var mess1;
        var day = new Date()
        var hr = day.getHours()
        if ((hr >= 0) && (hr <= 4))
            mess1 = "夜深了，注意身体！"
        if ((hr >= 4) && (hr < 7))
            mess1 = " 清晨好，早睡早起！"
        if ((hr >= 7) && (hr < 12))
            mess1 = "上午好，一天8杯水"
        if ((hr >= 12) && (hr <= 19))
            mess1 = "下午好，工作顺利！"
        if ((hr >= 19) && (hr <= 23))
            mess1 = "晚上好，别太累了！"
        return mess1
    },
    MD5: function (str) {
        return hex_md5(str);
    },
    dateTime: function (t) {
        if (t === undefined) {
            t = new Date().getTime();
        } else if (typeof t == 'string' && ['+', '-'].indexOf(t.substr(0, 1)) > -1 && isFinite(t)) {
            t = new Date().getTime() + (t * 1000);
        } else if (typeof t == 'number') {
            //
        } else {
            throw 't type error.';
        }
        var nowDate = new Date(t);
        var year = nowDate.getFullYear();
        var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1) : nowDate.getMonth() + 1;
        var date = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate.getDate();
        var hour = nowDate.getHours() < 10 ? "0" + nowDate.getHours() : nowDate.getHours();
        var minute = nowDate.getMinutes() < 10 ? "0" + nowDate.getMinutes() : nowDate.getMinutes();
        var second = nowDate.getSeconds() < 10 ? "0" + nowDate.getSeconds() : nowDate.getSeconds();
        return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
    },
    utter: function (e) {
        uim.utterThis = uim.utterThis || (!SpeechSynthesisUtterance ? null : new window.SpeechSynthesisUtterance());
        if (!uim.utterThis) {
            console.warn('浏览器不支持：SpeechSynthesisUtterance');
            return false;
        }
        if (window.speechSynthesis.speaking) {
            window.speechSynthesis.cancel();
        }
        uim.utterThis.rate = e.rate;
        uim.utterThis.text = e.text;
        window.speechSynthesis.speak(uim.utterThis);
        return true;
    },
    url_search: function (url) {
        var e = {};
        if (location.search) {
            var search_arr = (url ? url : location.search).split('?')[1].split('&');
            var tmp_search;
            for (var i = 0; i < search_arr.length; i++) {
                tmp_search = search_arr[i].split('=');
                e[tmp_search[0]] = tmp_search[1];
            }
        }
        return e;
    },
    getStrBT: function (startStr, StrS, endStr, c) {

        // var str1 = StrS.split(startStr);
        // str1.shift();
        // if (str1.length == 0) {
        //     return '';
        // }
        // var str2 = str1.join(startStr).split(endStr);
        // var l = c ? startStr : '';
        // var r = c ? endStr : '';
        // return l + (str2.length == 1 ? '' : str2[0]) + r;

        var lenS, lenE, tmpS, tmpE;
        lenS = startStr.length;
        lenE = endStr.length;
        tmpS = StrS.indexOf(startStr) + lenS;
        if (tmpS == lenS - 1) return '';
        tmpE = StrS.indexOf(endStr, tmpS)
        if (tmpE < 0) return '';
        var l = '', r = '';
        if (c) {
            l = startStr;
            r = endStr;
        }
        return l + StrS.substr(tmpS, tmpE - tmpS) + r;
    },
    array_column: function (arr, col) {
        var tmp_arr = [];
        for (var tmp_i = 0; tmp_i < arr.length; tmp_i++) {
            tmp_arr.push(arr[tmp_i][col]);
        }
        return tmp_arr;
    },
    array_column_kv: function (arr, k, v) {
        // 如果没有传入v则返回整个元素
        var tmp_obj = {};
        for (var tmp_i = 0; tmp_i < arr.length; tmp_i++) {
            tmp_obj['_' + arr[tmp_i][k]] = v ? arr[tmp_i][v] : arr[tmp_i];
        }
        return tmp_obj;
    },
    error_log: {},
    on_error: function (msg, url, line, col, error) {
        console.error('uim catch the error((msg, url, line, col, error):', msg, url, line, col, error);
        if (typeof uim.error_handle.window_error == 'function') {
            uim.error_handle.window_error(msg, url, line, col, error)
        } else if (uim.error_handle.window_error === true) {
            uim.api.emit({
                code: 'error.report',
                data: {
                    client_log_time: Math.ceil(new Date().getTime() / 1000),
                    msg: msg,
                    url: location.href + ':' + url,
                    line: line,
                    col: col,
                    error_stack: error ? error.stack : 'null',
                    // times: 1,
                },
                success: function (data) {
                    console.info('js错误已上报')
                    if (data.stop) {
                        uim.error_handle.window_error = false;
                        console.info('已停止主动上报js错误信息')
                    }
                },
                error: function (err) {
                    console.warn('上报js错误信息时发生错误', err)
                    uim.error_handle.window_error = false;
                    console.info('已停止主动上报js错误信息')
                    return true;
                }
            })
        } else {
            console.log('未上报js错误信息')
        }
    },

    render: function (obj) {

        //global.template_render_tags=template_render_tags;
        var template_config = {
            begin: '{#', // ${, <%=
            end: '#}', // %>  // 如果是单行标签则使用 /}，否则必须使用{/end}
            // begin 与 end 不能相同也不能包含，不能使用小括号、空格及斜杠（属性之间使用空格，函数参数使用小括号）
            key_with: '.', // ->
            //fun_start: '.', // 函数可使用标签形式：{fun name="" args="" /}
            //fun_d: ['(',',',')'], // 无其他情形，无需定义

            // include可以使用内置函数方式
            // 将if与volist视为表达式，trim后以空格隔开为数组
            // -如果仅有一个元素，则包含fun_d[0,2]为函数，否则为变量
            // -如果不止一个元素，则第一个为标签名，以斜杠加标签名取结尾标签并调用相应函数解析
            max_include_template: 6, //最大包含次数
            max_render: 150, //最大渲染次数（递归）
            timeout: 1500, //超时毫秒
        };
        //global.template_config=template_config;

        var get_key_by_string = function (data_arr, key_str, split_str) {
            var key_arr = key_str.split(split_str);
            var e;
            for (var j = 0; j < data_arr.length; j++) {

                e = data_arr[j];
                if (e[key_arr[0]] === undefined) {
                    continue;
                }
                for (var i = 0; i < key_arr.length; i++) {
                    if (e === undefined) return e;
                    e = e[key_arr[i]];
                }
                break;
            }
            return e;
        };
        // 变量处理函数
        var template_render_v = function (data_arr, content) {

            var render_data = content.split(template_config.begin);
            var v_name, v_arr, v_data;
            if (render_data.length == 1) {
                return {
                    error: '',
                    content: content,
                };
            }
            for (var j = 0; j < data_arr.length; j++) {
                for (k in data_arr[j]) {
                    // console.log('var ' + k + '=data_arr[j][k];');
                    // todo k必须为符合变量命名规则
                    // todo 记录所有执行过的eval
                    eval('var ' + k + '=data_arr[j][k];');
                }
            }
            for (var i = 1; i < render_data.length; i++) {
                v_arr = render_data[i].split(template_config.end);
                v_exp = v_arr.shift();
                if (v_arr.length) {
                    try {
                        v_data = eval(v_exp);
                        // console.log(v_exp, v_data)
                    } catch (e) {
                        console.error('执行条件错误：' + v_exp, content)
                        console.log(e)
                        return {
                            error: '执行条件错误：' + v_exp,
                        }
                    }
                    render_data[i] = v_data + v_arr.join(template_config.end);
                } else {
                    render_data[i] = template_config.begin + render_data[i];
                }
            }
            return {
                error: '',
                content: render_data.join(''),
            };
            ///////////////////////////////////
            var render_data = content.split(template_config.begin);
            var v_name, v_arr, v_data;
            if (render_data.length == 1) {
                return {
                    error: '',
                    content: content,
                };
            }
            for (var i = 1; i < render_data.length; i++) {
                v_arr = render_data[i].split(template_config.end);
                v_name = v_arr.shift();
                if (v_arr.length && v_name.indexOf(' ') + v_name.indexOf('\n') + v_name.indexOf('/') == -3) {
                    v_data = get_key_by_string(data_arr, v_name, template_config.key_with);
                    if (v_data === undefined && template_config.strict_mode) {
                        return {
                            error: '变量未定义：' + v_name,
                            content: ''
                        };
                    }
                    render_data[i] = (v_data === undefined ? '' : v_data) + v_arr.join(template_config.end);
                } else {
                    render_data[i] = template_config.begin + render_data[i];
                }
            }
            return {
                error: '',
                content: render_data.join(''),
            };
        };
        var template_render = function (data_arr, content, call_back) {
            var getTemplateCount = 0;
            var render_error = false;
            var template_render_tags = {
                'include': function (local_data, content, call_back) {
                    if (obj.template_type != 1 && obj.template_type != 2) {
                        return call_back({
                            error: '不支持include标签',
                            content: ''
                        })
                    }
                    var template_code = uim.getStrBT('template="', content, '"');
                    if (template_code == '') {
                        return call_back({
                            error: 'include：缺少template属性',
                            content: ''
                        })
                    }
                    var getTemplate = function (template_code, call_back) {
                        getTemplateCount++;
                        // console.log("getTemplateCount13", getTemplateCount)

                        if (getTemplateCount > template_config.max_include_template) {
                            return call_back({
                                error: '嵌套模板层数超出限制：' + template_config.max_include_template,
                            });
                        }
                        if (obj.template_type == 1) {
                            call_back({
                                error: '',
                                content: document.getElementById(template_code).innerHTML
                            });
                        } else if (obj.template_type == 2) {

                            // uim.api.emit({
                            //     code: 'uim>template.render.get/1_0',
                            //     data: {
                            //         template_code: template_code,
                            //     },
                            //     cache: obj.cache,
                            //     success: function (data) {
                            //         call_back({
                            //             error: '',
                            //             content: data.template_content
                            //         });
                            //     },
                            //     error: function (err) {
                            //         call_back({
                            //             error: '获取模板失败：' + template_code + '\n' + err.description,
                            //             content: ''
                            //         });
                            //     }
                            // })
                            uim.get_view(template_code, function (data) {
                                call_back({
                                    error: '',
                                    content: data.template_content
                                });
                            }, function (err) {
                                call_back({
                                    error: '获取模板失败：' + template_code + '\n' + err.description,
                                    content: ''
                                });
                            });
                        }
                    }
                    getTemplate(template_code, function (template) {
                        if (template.error) {
                            call_back(template);
                        } else {
                            render_content(local_data, template.content, function (render_result) {
                                // console.log('include data', render_result)
                                call_back(render_result);
                            });
                        }
                    })
                },
                'if': function (data_arr, content, call_back) {
                    // console.log('exec-if:' + content + '/');
                    //console.log(uim.getStrBT('else}', content, '{/if}'));

                    //将数据对象中的每个键名定义到变量，便于if中执行判断
                    for (var j = 0; j < data_arr.length; j++) {
                        var local_data = data_arr[j];
                        for (var i in local_data) {
                            eval('var ' + i + ' = local_data["' + i + '"]')
                        }
                    }

                    var exec_if_exp = function (exp) {
                        if (exp === '') {
                            return true;
                        }
                        // console.log('exec_if_exp: ' + exp)
                        try {
                            if (!!eval(exp)) {
                                return true;
                            } else {
                                return false;
                            }
                        } catch (e) {
                            // console.log('catch error--------------\n', e)
                            return {
                                error: '执行条件错误：' + exp,
                            }
                        }
                    }
                    // todo 解决传入变量名与程序变量名冲突的问题，比如程序变量加下划线
                    //将if标签中的起始if与所有elseif替换成else，最后使用else隔成数组
                    var tmp_content = content
                        .split(template_config.begin + 'else' + template_config.end)
                        .join(template_config.begin + 'else 1' + template_config.end)
                        .substr(0, content.length - template_config.begin.length - 3 - template_config.end.length)
                        .split(template_config.begin + 'elseif ')
                        .join(template_config.begin + 'else ')
                        .split(template_config.begin + 'else ');
                    tmp_content[0] = tmp_content[0].substr(template_config.begin.length + 3);
                    // console.log('exec_if_content', tmp_content)
                    var child_if_count = 0; // 与当前起始标签相抵为0

                    var cross_for_content = '';
                    var tmp_if = null;

                    for (var i = 0; i < tmp_content.length; i++) {
                        // console.log(i, tmp_content[i], child_if_count)


                        if (cross_for_content) {
                            //如果有跨循环的内容
                            // console.log('有跨循环的内容')

                            //则需要把当前表达式添加回去
                            cross_for_content += template_config.begin + 'else ' + tmp_content[i];

                            child_if_count += tmp_content[i].split(template_config.begin + 'if ').length - 1;
                            child_if_count -= tmp_content[i].split(template_config.begin + '/if' + template_config.end).length - 1;
                            // console.log('if嵌套：', i, child_if_count)
                            //如果if嵌套结束
                            if (child_if_count == 0) {

                                // console.log('if嵌套结束')
                                render_content(data_arr, cross_for_content, function (render_result) {
                                    // console.log('if data', render_result)
                                    call_back(render_result)
                                });
                                return;
                            }
                        } else {
                            //如果没有跨循环的内容，说明还没有找到满足的条件
                            // console.log('没有跨循环的内容，说明还没有找到满足的条件')
                            var tmp_if_data = tmp_content[i].split(template_config.end);
                            // console.log('tmp_if_data', tmp_if_data)
                            var if_exp = tmp_if_data.shift();

                            var current_child_if_count = child_if_count;
                            child_if_count += tmp_content[i].split(template_config.begin + 'if ').length - 1;
                            child_if_count -= tmp_content[i].split(template_config.begin + '/if' + template_config.end).length - 1;
                            // console.log('if嵌套：', i, child_if_count)

                            //如果没有if嵌套
                            if (current_child_if_count == 0) {
                                // console.log('没有if嵌套')
                                //执行if条件
                                tmp_if = exec_if_exp(if_exp);
                                // console.log('执行if条件exec_tmp_if', if_exp, tmp_if)
                                if (tmp_if === true) {
                                    if (child_if_count == 0) {
                                        //直接渲染当前标签内容并返回
                                        render_content(data_arr, tmp_if_data.join(template_config.end), function (render_result) {
                                            // console.log('if data', render_result)
                                            call_back(render_result)
                                        });
                                        return;
                                    } else {
                                        //如果发生了嵌套，则需要把当前内容添加到变量，由下次for执行
                                        cross_for_content = tmp_if_data.join(template_config.end);
                                    }
                                } else if (tmp_if.error) {
                                    call_back(tmp_if);
                                    return;
                                } else {
                                    //继续执行for循环
                                }
                            }

                        }
                    }
                    call_back({
                        error: '',
                        content: ''
                    });
                    return;
                    // call_back({
                    //     error: '缺少结束标签（嵌套）：if（rendering）',
                    //     content: ''
                    // });
                },
                'for': function (data_arr, content, call_back) {
                    // console.log('exec-for:' + content + '/');

                    //将数据对象中的每个键名定义到变量，便于if中执行判断
                    for (var j = 0; j < data_arr.length; j++) {
                        var local_data = data_arr[j];
                        for (var i in local_data) {
                            eval('var ' + i + ' = local_data["' + i + '"]')
                        }
                    }

                    var for_data_get = uim.getStrBT(template_config.begin + 'for ', content, template_config.end);
                    var arr_data = uim.getStrBT('arr="', for_data_get, '"');
                    var obj_data = uim.getStrBT('obj="', for_data_get, '"');
                    var key_data = uim.getStrBT('key="', for_data_get, '"');
                    var index_name = uim.getStrBT('index="', for_data_get, '"');
                    // 如果obj和arr都没有设置，或者key没有设置
                    if ((!obj_data && !arr_data) || !key_data) {
                        call_back({
                            error: 'for数据获取错误',
                            content: ""
                        })
                        return;
                    }
                    var for_data = get_key_by_string(data_arr, obj_data || arr_data, ".");
                    // todo 是否改成遍历定义变量后直接eval而不使用get_key_by_string
                    if ((!for_data || for_data.length === undefined) || typeof for_data != 'object') {
                        try {
                            for_data = eval(obj_data || arr_data);
                        } catch (e) {
                            console.error('未提供渲染数据，且eval错误：', obj_data || arr_data)
                        }
                    }
                    if ((for_data === undefined || for_data.length === undefined) || typeof for_data != 'object') {
                        call_back({
                            error: 'for数据变量未定义' + (obj_data || arr_data),
                            // error: '',
                            content: ""
                        })
                        return;
                    }
                    if (for_data.length === 0 || typeof for_data != 'object') {
                        call_back({
                            error: '',
                            content: '',
                        })
                        return;
                    }
                    var for_return = [];
                    var error_break = false;
                    var render_for_i = function (i, data, content) {

                        //这里有可能包含异步（引入模板），所以封装成小函数来传i变量
                        render_content(data, content, function (render_result) {
                            // console.log('for data', render_result)
                            if (render_result.error) {
                                error_break = render_result;
                                call_back(render_result);
                                return;
                            } else {
                                for_return[i] = render_result.content;
                                if (for_return.length == for_data.length) {
                                    call_back({
                                        error: '',
                                        content: for_return.join('')
                                    });
                                }
                            }
                        });
                    }
                    if (obj_data) {
                        var i = 0;
                        for (var f in for_data) {
                            if (error_break) {
                                continue;
                            }
                            var local_data = {};
                            if (index_name) {
                                local_data[index_name] = i + 1;
                            }
                            local_data[key_data] = for_data[f];
                            var content_start = for_data_get.length + template_config.begin.length + template_config.end.length + 4;
                            // 'for '.length = 4
                            var content_end = content.length - content_start - template_config.begin.length * 2 - 4 - template_config.end.length * 2
                            // '/for'.length = 4
                            // console.log("for 34", [local_data].concat(data_arr))
                            render_for_i(i, [local_data].concat(data_arr), content.substr(content_start, content_end));
                            i++;
                        }
                    } else {

                        for (var i = 0; i < for_data.length; i++) {
                            if (error_break) {
                                continue;
                            }
                            var local_data = {};
                            if (index_name) {
                                local_data[index_name] = i + 1;
                            }
                            local_data[key_data] = for_data[i];
                            var content_start = for_data_get.length + template_config.begin.length + template_config.end.length + 4;
                            // 'for '.length = 4
                            var content_end = content.length - content_start - template_config.begin.length * 2 - 4 - template_config.end.length * 2
                            // '/for'.length = 4
                            // console.log("for 34", [local_data].concat(data_arr))
                            render_for_i(i, [local_data].concat(data_arr), content.substr(content_start, content_end));
                        }
                    }
                },
                each: function () {
                    //todo 可通过调试模式显示堆栈调用（提供local_data，渲染函数递归等信息）
                },
                i18n: function () {
                    //todo 直接作为属性使用，或在变量解析时直接套用函数？
                    // 区别于其他客户端js解决方案，在邮件、短彩信等其他不适合运行js的情形中使用
                },
                ignore: function (data_arr, content, call_back) {
                    var tmp_content = content;
                    //去除当前块的起始标签
                    tmp_content = tmp_content.split(template_config.end);
                    tmp_content.shift();
                    tmp_content = tmp_content.join(template_config.end);
                    // 去除当前块的结束标签
                    tmp_content = tmp_content.split(template_config.begin);
                    tmp_content.pop();
                    tmp_content = tmp_content.join(template_config.begin);

                    // 忽略块内的语法
                    call_back({
                        error: '',
                        content: tmp_content,
                    });
                }
            };
            //tag function

            var getTag = function (content, tag, start_pos) {
                //轻度获取标签：属性中使用引号、引号中有标签定界符、引号中的引号前有反斜杠等情形未处理
                // console.log('getTag:' + tag)

                var tmp_pos_begin = content.indexOf(template_config.begin + tag + ' ', start_pos);
                if (tmp_pos_begin == -1) {
                    return false;
                }
                var tmp_pos_end = content.indexOf(template_config.end, tmp_pos_begin);
                if (tmp_pos_end == -1) {
                    // return false;
                    return {
                        error: '缺少结束标签：' + tag,
                        content: ''
                    };
                }
                var end_pos = tmp_pos_end;
                //继续判断是否块级
                if (content.substr(tmp_pos_end - template_config.end.length + 1, 1) == '/') {
                    // console.log('带/的行级标签')
                    end_pos = tmp_pos_end + template_config.end.length;
                    return {
                        start_pos: tmp_pos_begin,
                        end_pos: end_pos,
                        name: tag,
                        content: content.substring(tmp_pos_begin, end_pos)
                    };
                } else {
                    // console.log('不带/的块级标签')
                    //块级结束
                    var block_end = template_config.begin + '/' + tag + template_config.end;
                    var find_block_end_from = -1;
                    // 将右侧内容使用结束标签分隔成数组
                    var tmp_content = content.substr(tmp_pos_end).split(block_end);
                    var child_tag_count = 0; // 与起始标签（tmp_pos_begin）相抵为0
                    for (var i = 0; i < tmp_content.length; i++) {
                        // console.log(i, child_tag_count)
                        child_tag_count += tmp_content[i].split(template_config.begin + tag + ' ').length - 1;
                        end_pos += tmp_content[i].length + block_end.length;

                        if (child_tag_count == i) {
                            return {
                                start_pos: tmp_pos_begin,
                                end_pos: end_pos,
                                name: tag,
                                content: content.substring(tmp_pos_begin, end_pos)
                            }
                        }
                    }
                    return {
                        error: '缺少结束标签（嵌套）：' + tag,
                        content: ''
                    };
                }
            }
            //取得指定标签内容（含标签部分）
            var getTagContent = function (content) {
                var first_tag = null, tmp_tag = null;
                // 遍历并搜索所有支持的标签，取得第一个标签起始位置
                for (var i in template_render_tags) {
                    //
                    tmp_tag = getTag(content, i, 0);
                    if (!tmp_tag) {
                        // 没有找到
                        continue;
                    } else {
                        //找到此标签

                        if (first_tag == null || tmp_tag.start_pos < first_tag.start_pos) {
                            first_tag = tmp_tag;
                        }
                    }
                }
                // console.log('找到第一个标签', first_tag)
                return first_tag;
            };
            var render_content = function (data_arr, content, call_back) {

                if (render_error) {
                    return;
                }
                var first_tag = getTagContent(content);
                if (first_tag === null) {
                    //没有找到标签，直接解析变量
                    // console.log('没有找到标签，直接解析变量')
                    var no_tag = template_render_v(data_arr, content);
                    //console.log('no_tag', no_tag)
                    call_back(no_tag);
                } else if (first_tag.error) {
                    //查找标签时发现模板错误
                    // console.log('查找标签时发现模板错误')
                    render_error = true;
                    call_back(first_tag);
                } else {
                    // console.log('渲染标签', first_tag);

                    template_render_tags[first_tag.name](data_arr, first_tag.content, function (middle_content) {
                        // console.log('渲染完毕：middle_content', middle_content)
                        //解析错误
                        if (middle_content.error) {
                            // console.log('解析错误', middle_content)
                            render_error = true;
                            call_back(middle_content);
                        }
                        //继续解析
                        else {
                            //console.log('继续解析', first_tag, middle_content)
                            var left_content = template_render_v(data_arr, content.substring(0, first_tag.start_pos));
                            var right_content = content.substr(first_tag.end_pos);
                            // console.log('left_content', left_content)
                            // console.log('middle_content', middle_content)
                            // console.log('right_content', right_content)
                            if (right_content == '') {
                                call_back({
                                    error: '',
                                    content: left_content.content + middle_content.content
                                });
                                return;
                            }
                            render_content(data_arr, right_content, function (right_render_result) {
                                // console.log('++++++++++render_content\n', left_content.content + middle_content.content, right_render_result.content);
                                //解析错误
                                if (right_render_result.error) {
                                    // console.log('解析错误', right_render_result)
                                    render_error = true;
                                    call_back(right_render_result);
                                } else {
                                    call_back({
                                        error: '',
                                        content: left_content.content + middle_content.content + right_render_result.content
                                    });
                                }

                            });
                        }
                    });
                }
            }
            render_content(data_arr, content, call_back);
        };

        if (!obj || !obj.template || !obj.data) {
            return console.error('未传入模板或数据');
        }


        if (!obj.context) {
            return console.error('未传入上下文')
        }
        var work_template_content = '';
        var window = obj.context;
        var document = obj.context.document;
        switch (obj.template_type) {
            case 1:
                work_template_content = document.getElementById(obj.template).innerHTML;
                break;
            case 2:
                work_template_content = '{#include template="' + obj.template + '" /#}';
                break;
            default:
                work_template_content = obj.template;
        }

        template_render([obj.data], work_template_content, function (result) {
            obj.target && (document.getElementById(obj.target).innerHTML = result.error || result.content);
            typeof obj.done == 'function' && obj.done(result);
        });

    },
    get_view: function (view_name, success, error) {
        var cache_view = uim.view_list[view_name];
        // 如果有本地缓存的视图，则直接返回
        if (cache_view) {
            return success(cache_view);
        }

        // 如果view_src不为空，则作为url前缀
        if (uim.config.view_src) {
            var xhr = new XMLHttpRequest();
            xhr.onload = function () {
                if (xhr.status === 200 || xhr.status === 304) {
                    if (uim.init_param.cache_view) {
                        uim.view_list[view_name] = ''; //this.responseText； // ajax.view_src模式下不保存完整内容，由浏览器缓存
                        uim.save('view_list');// 如果之前缓存的和现在的不一样则更新缓存
                    }
                    success({ template_content: this.responseText })
                } else {
                    console.log('xhr get view error.')
                    error({
                        description: 'xhr get view error.'
                    })
                }
            }
            xhr.open('GET', uim.config.view_src + '/' + view_name + '.html', true);
            xhr.onerror = function () {
                console.log('xhr get view error.')
                error({
                    description: 'xhr get view error.'
                })
            };
            (cache_view === undefined || uim.init_param.cache_view === false) && xhr.setRequestHeader('If-Modified-Since', '0');
            xhr.send();
        } else {
            uim.api.emit({
                code: 'view.get',
                data: {
                    view_name: view_name
                },
                success: function (data) {
                    if (uim.init_param.cache_view) {
                        uim.view_list[view_name] = data.template_content; // todo 是否写入localStorage
                        uim.save('view_list');
                    }
                    success(data)
                },
                error: function (err) {
                    console.error('api get view error.')
                    error({
                        description: 'api get view error.'
                    })
                }
            })
        }
    },
    view_list: {},
}